package com.cnswhy.cloud.server.iot.utils;


import com.alibaba.fastjson.JSON;
import com.cnswhy.cloud.common.redis.service.RedisService;
import jodd.util.Base64;
import lombok.extern.slf4j.Slf4j;
import org.bouncycastle.jce.provider.BouncyCastleProvider;

import javax.crypto.Cipher;
import java.io.ByteArrayOutputStream;
import java.math.BigInteger;
import java.net.URLDecoder;
import java.security.*;
import java.security.interfaces.RSAPrivateKey;
import java.security.interfaces.RSAPublicKey;
import java.security.spec.InvalidKeySpecException;
import java.security.spec.PKCS8EncodedKeySpec;
import java.security.spec.RSAPrivateKeySpec;
import java.security.spec.RSAPublicKeySpec;

/**
 * 描述: js与后端交互:RSA 工具类。提供加密，解密，生成密钥对等方法。
 * 版本: 1.0 
 * 创建日期: 2016年9月21日 
 * 创建时间: 下午2:01:48
 */
@Slf4j
public class RSAUtils
{
    
    /**
     * redis keypair key
     */
    private static final String LOGIN_RSA_KEY_KEYPAIR = "login_rsa_key_keypair";
    
    /**
     *  KEY长度
     */
    private static final int KEY_SIZE = 1024;
    private static BouncyCastleProvider bouncyCastleProvider = null;
    public static synchronized BouncyCastleProvider getInstance() {
        if (bouncyCastleProvider == null) {
            bouncyCastleProvider = new BouncyCastleProvider();
        }
        return bouncyCastleProvider;
    }

    /**
     * 
     * @function:  生成密钥对
     * @return
     * @throws Exception KeyPair   
     * @exception 
     * @author:	张进飞
     * @since  1.0.0
     */
    public static KeyPair generateKeyPair(RedisService redisService) throws Exception
    {
        //判断redis中是否存在
        if (true == redisService.exists(LOGIN_RSA_KEY_KEYPAIR))
        {
            return getKeyPair(redisService);
        }
        else
        {
            try
            {
                KeyPairGenerator keyPairGen = KeyPairGenerator.getInstance("RSA",
                        getInstance());
                keyPairGen.initialize(KEY_SIZE, new SecureRandom());
                KeyPair keyPair = keyPairGen.generateKeyPair();
                //保存对象
                saveKeyPair(keyPair, redisService);
                return keyPair;
            }
            catch (Exception e)
            {
                throw new Exception(e.getMessage());
            }
        }
    }
    
    public static KeyPair getKeyPair(RedisService redisService) throws Exception
    {
        Object object = redisService.get(LOGIN_RSA_KEY_KEYPAIR);
        log.info("vaule:{}", JSON.toJSONString(object));
        KeyPair keyPair = JSON.parseObject(JSON.toJSONString(object),KeyPair.class);
        return keyPair;
        //return (KeyPair) redisService.get(LOGIN_RSA_KEY_KEYPAIR);
    }
    
    public static void saveKeyPair(KeyPair kp, RedisService redisService) throws Exception
    {
        //保存在redis中
        redisService.set(LOGIN_RSA_KEY_KEYPAIR, JSON.toJSONString(kp));//保存对象到redis当中
    }
    
    /**
     * 
     * @function:	生成公钥  
     * @param modulus
     * @param publicExponent
     * @return
     * @throws Exception RSAPublicKey   
     * @exception 
     * @author:		张进飞   
     * @since  1.0.0
     */
    public static RSAPublicKey generateRSAPublicKey(byte[] modulus, byte[] publicExponent) throws Exception
    {
        KeyFactory keyFac = null;
        try
        {
            keyFac = KeyFactory.getInstance("RSA", getInstance());
        }
        catch (NoSuchAlgorithmException ex)
        {
            throw new Exception(ex.getMessage());
        }
        
        RSAPublicKeySpec pubKeySpec = new RSAPublicKeySpec(new BigInteger(modulus), new BigInteger(publicExponent));
        try
        {
            return (RSAPublicKey) keyFac.generatePublic(pubKeySpec);
        }
        catch (InvalidKeySpecException ex)
        {
            throw new Exception(ex.getMessage());
        }
    }
    
    /**
     * 
     * @function: 生成私钥 
     * @param modulus
     * @param privateExponent
     * @return
     * @throws Exception RSAPrivateKey   
     * @exception 
     * @author:		张进飞   
     * @since  1.0.0
     */
    public static RSAPrivateKey generateRSAPrivateKey(byte[] modulus, byte[] privateExponent) throws Exception
    {
        KeyFactory keyFac = null;
        try
        {
            keyFac = KeyFactory.getInstance("RSA", getInstance());
        }
        catch (NoSuchAlgorithmException ex)
        {
            throw new Exception(ex.getMessage());
        }
        
        RSAPrivateKeySpec priKeySpec = new RSAPrivateKeySpec(new BigInteger(modulus), new BigInteger(privateExponent));
        try
        {
            return (RSAPrivateKey) keyFac.generatePrivate(priKeySpec);
        }
        catch (InvalidKeySpecException ex)
        {
            throw new Exception(ex.getMessage());
        }
    }
    
    /**
     * 
     * @function:  
     * @param pk 加密的密钥
     * @param data  待加密的明文数据 
     * @return 加密后的数据
     * @throws Exception byte[]   
     * @exception 
     * @author:		张进飞
     * @since  1.0.0
     */
    
    public static byte[] encrypt(PublicKey pk, byte[] data) throws Exception
    {
        try
        {
            Cipher cipher = Cipher.getInstance("RSA", getInstance());
            cipher.init(Cipher.ENCRYPT_MODE, pk);
            int blockSize = cipher.getBlockSize();// 获得加密块大小，如：加密前数据为128个byte，而key_size=1024
            // 加密块大小为127
            // byte,加密后为128个byte;因此共有2个加密块，第一个127
            // byte第二个为1个byte
            int outputSize = cipher.getOutputSize(data.length);// 获得加密块加密后块大小
            int leavedSize = data.length % blockSize;
            int blocksSize = leavedSize != 0 ? data.length / blockSize + 1 : data.length / blockSize;
            byte[] raw = new byte[outputSize * blocksSize];
            int i = 0;
            while (data.length - i * blockSize > 0)
            {
                if (data.length - i * blockSize > blockSize)
                    cipher.doFinal(data, i * blockSize, blockSize, raw, i * outputSize);
                else
                    cipher.doFinal(data, i * blockSize, data.length - i * blockSize, raw, i * outputSize);
                // 这里面doUpdate方法不可用，查看源代码后发现每次doUpdate后并没有什么实际动作除了把byte[]放到
                // ByteArrayOutputStream中，而最后doFinal的时候才将所有的byte[]进行加密，可是到了此时加密块大小很可能已经超出了
                // OutputSize所以只好用dofinal方法。
                
                i++;
            }
            return raw;
        }
        catch (Exception e)
        {
            throw new Exception(e.getMessage());
        }
    }
    
    /**
     * 
     * @function:  解密
     * @param pk 解密的密钥
     * @param raw 已经加密的数据
     * @return
     * @throws Exception byte[]   
     * @exception  Exception
     * @author:		张进飞   
     * @since  1.0.0
     */
    public static byte[] decrypt(PrivateKey pk, byte[] raw) throws Exception
    {
        try
        {
            //            //
            byte[] keyBytes = Base64.decode("MIICdwIBADANBgkqhkiG9w0BAQEFAASCAmEwggJdAgEAAoGBANCIALFD7vbIXbPby9OKzwqyh6X9HPRaWpmNXLr5L+QZssctjuCbML5FEV0D9S4VGw7FreelsIt/mxr5EHLW97OvQnR4MyXBn6/oKUoAMuC/envhWHFHw6d5Z+dhkExgnDWkKlgk5UrFNapkorRdEgin062KnqTtJm7SaJ37XAuDAgMBAAECgYARQkUmzSCqUL030hsY2CLrpG86qzjDkCjYdZoCDynjpWBypxztdQEZqYPweil6lc+ZlbEF4ssHHwzgzh7w497RqQ9G2wmSNuH+deqCAyfq4tHpa0W4p5RCzVG2cDCOf3l0Y6gZ/JRQmjk6KpxNuintRojf5PA3N0eYSnWKX4u++QJBAPmKOKY2MaoYFZ5nfdiyC/i5KBglo8e0qMr21cdiFPg2Tl0OfnHaK41du745tF0O6i126CmIyNr7AXduPPVW8TkCQQDV7gEVTbgIDuZNgYTT2awg9mS8K5SSS2+YjRY2ma3pp0Ni7oVD6L1t6SQBE5myp3TTh2Se2yYmt/YvipeIHe6bAkEAxbrKrJmgvOEcsEndJEngxpMgvBGHN4Vlgn4YY0bKyuttKLuFYf24KOovC8p5qotY7/GagN7MGQhv3wzQ7K6ZsQJABHInz5uH4ZL9skqoygZSD3VPbqGxOsEalp8IASZ8eMijG+E9H8YseaZIu0T/Arl8MhE0RCDl0BQup5pHv+pvVQJBANN1Y8Xm/5wsKG8LlAF81nnvHxkBlmBopISSJPuLKaRm/acOY2s7VuumO7nBNPt6Qyas99AywbDYblIoAXLRdw8=");
            PKCS8EncodedKeySpec pkcs8KeySpec = new PKCS8EncodedKeySpec(keyBytes);
            KeyFactory keyFactory = KeyFactory.getInstance("RSA");
            Key privateK = keyFactory.generatePrivate(pkcs8KeySpec);

            Cipher cipher = Cipher.getInstance("RSA", getInstance());
            cipher.init(cipher.DECRYPT_MODE, privateK);
            int blockSize = cipher.getBlockSize();
            ByteArrayOutputStream bout = new ByteArrayOutputStream(64);
            int j = 0;
            
            while (raw.length - j * blockSize > 0)
            {
                bout.write(cipher.doFinal(raw, j * blockSize, blockSize));
                j++;
            }
            return bout.toByteArray();
        }
        catch (Exception e)
        {
            throw new Exception(e.getMessage());
        }
    }
    
    /**
     * 
     * @function:   对数据进行解密的公用方法(包过解决乱码等一些问题)
     * @param privateKey 私钥
     * @param result	要解密的内容
     * @return	解密成功后的数据
     * @throws Exception String   
     * @exception 
     * @author:		张进飞   
     * @since  1.0.0
     */
    public static String decryptString(PrivateKey privateKey, String result) throws Exception
    {
        
        String pwd = null;
        try
        {
            byte[] en_result = hexStringToBytes(result);
            byte[] de_result = decrypt(privateKey, en_result);
            //			System.out.println(new String(de_result));
            StringBuffer sb = new StringBuffer();
            sb.append(new String(de_result));
            pwd = sb.reverse().toString();
            pwd = URLDecoder.decode(pwd, "UTF-8");//
        }
        catch (Exception e)
        {
            throw e;
        }
        
        return pwd;
    }
    public static String decryptString(String result) throws Exception
    {

        String pwd = null;
        try
        {
            byte[] en_result = hexStringToBytes(result);
            byte[] de_result = decrypt(null, en_result);
            //			System.out.println(new String(de_result));
            StringBuffer sb = new StringBuffer();
            sb.append(new String(de_result));
            pwd = sb.reverse().toString();
            pwd = URLDecoder.decode(pwd, "UTF-8");//
        }
        catch (Exception e)
        {
            throw e;
        }

        return pwd;
    }
    
    /**
     *  秘文传过来一共有256个字符，通过转成字节数组，同样的明文，有时传过来转成的字节数组长度为128，有的时候却又是129，
     *说是这个转字节数组的方式问题嘛，我就换了一个方式，将其稳定在128位
     * @function: 解决byte[] en_result = new BigInteger(result, 16).toByteArray(); 算法不稳定问题  
     * @param hexString
     * @return byte[]   
     * @exception 
     * @author:wujun
     * @since  1.0.0
     */
    public static byte[] hexStringToBytes(String hexString)
    {
        if (hexString == null || hexString.equals(""))
        {
            return null;
        }
        hexString = hexString.toUpperCase();
        int length = hexString.length() / 2;
        char[] hexChars = hexString.toCharArray();
        byte[] d = new byte[length];
        for (int i = 0; i < length; i++)
        {
            int pos = i * 2;
            d[i] = (byte) (charToByte(hexChars[pos]) << 4 | charToByte(hexChars[pos + 1]));
        }
        return d;
    }
    
    /**  
     * Convert char to byte  
     * @param c char  
     * @return byte  
     */
    private static byte charToByte(char c)
    {
        return (byte) "0123456789ABCDEF".indexOf(c);
    }
}
